package grails.coherence.enhancer

import org.codehaus.groovy.grails.commons.DomainClassArtefactHandler;

import org.codehaus.groovy.grails.commons.GrailsDomainClass;

import grails.coherence.enhancer.comparator.GroovyComparator

import groovy.lang.Closure

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory

import org.codehaus.groovy.grails.commons.GrailsClass;
import org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor;

import com.tangosol.net.ConfigurableCacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.util.aggregator.Count;
import com.tangosol.util.processor.PreloadRequest;

import static org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.*

/**
 * Support class for application bootstrapping. 
 * 
 * All the MOP stuff goes here.   
 * 
 * @author Massimiliano Mazzarolo
 *
 */
class BootstrapHelper {
	
	def application
	def cacheFactory
	
	private static Log log = LogFactory.getLog(BootstrapHelper)

	/*
	 * The default naming strategy for caches.
	 */
	private Closure defaultCacheNamingStrategy = { it.fullName
	}
	
	void addCachingToDomainClasses() {		
		application.domainClasses.each { GrailsClass grailsClass ->
						
			final Map defaultConfig = [			     
				cachename : defaultCacheNamingStrategy,		
				populate : false,
				keepSync: true
			]
			
			def cachingConfig = grailsClass.getPropertyValue('caching', Map) ?: 
					(grailsClass.getPropertyValue('caching', Boolean) ? defaultConfig : null)
			
			if (cachingConfig) {
				
				// Register cache method
				def cacheName = cachingConfig.cachename
				if (cachingConfig.cachename instanceof Closure) {
					cacheName = cachingConfig.cachename.call(grailsClass)
				}
				registerCacheMethod(cacheName, grailsClass)
				
				// pre-load cache content  
				if (cachingConfig.populate) {
					preloadDomainCache(grailsClass)
				}
				
				if (cachingConfig.keepSync) {
					// Register events on GORM classes that keeps cache in sync with
					addEventListener(grailsClass)
				}
			}
		}
	}

	private void wrapEvent(GrailsDomainClass domainClass, String eventName, Closure cacheEventHandler) {
				
			def metaClass = domainClass.clazz.metaClass
			
			def wrappedEventClosure = { GrailsDomainClass clazz, Closure handler ->
								
				handler.delegate = delegate
				hanlder.resolveStrategy = Closure.DELEGATE_FIRST
				
				switch(handler.maximumNumberOfParameters) {
					case 0:
						handler()
						break
					case 1:
						handler(clazz)
						break
					default:
						throw new IllegalArgumentException("Closure for event $eventName of class ${clazz.clazz} can take 0 or 1 parameter at most")
				}
				
				if (clazz.hasProperty(eventName)) { 
					def existingEventClosure = delegate."$eventName"			
					existingEventClosure.delegate = delegate						
					existingEventClosure.call()
				}	
			}
		
			metaClass."$eventName" = wrappedEventClosure.curry(domainClass, cacheEventHandler)
			
			
			// domainClass.setPropertyValue(eventName, wrappedEventClosure.curry(domainClass, cacheEventHandler))
		}
		

	
	private void addEventListener(GrailsDomainClass domainClass) {
		
		wrapEvent(domainClass, AFTER_INSERT_EVENT) { dc -> 
			def id = delegate."${dc.identifier.name}"
			dc.clazz?.cache().invoke(id, new PreloadRequest())						
		}
				
		wrapEvent(domainClass, AFTER_DELETE_EVENT) { dc -> 
			def id = delegate."${dc.identifier.name}"
			dc.clazz?.cache().remove(id)						
		}
		
		wrapEvent(domainClass, AFTER_UPDATE_EVENT) { dc -> 
			def id = delegate."${dc.identifier.name}"
			dc.clazz?.cache().with { 
				remove(id)
				invoke(id, new PreloadRequest())
			}						
		}
		
		/*
		wrapEvent(domainClass, "afterInsert") { dc -> 
			def id = dc.getPropertyValue(dc.identifier.name)
			dc.clazz?.cache().invoke(id, new PreloadRequest())						
		}
		
		def existingAfterInsertClosure = domainClass.getPropertyValue("afterInsert")
		

		def afterInsertCacheClosure = { ->
			def id = domainClass.getPropertyValue(domainClass.identifier.name)
			domainClass.clazz?.cache().invoke(id, new PreloadRequest())
			if (existingAfterInsertClosure) {
				existingAfterInsertClosure.call()
			}			
		}
		*/
		
	}

	/**
	 * Add a [Domain].cache() static method to the given domain class that returns
	 * a NamedCache instance.
	 * 
	 * @param name the cache name.
	 * @param grailsClass the domain class.
	 * 
	 */
	private void registerCacheMethod(String name, GrailsClass grailsClass) {		
		grailsClass.clazz.metaClass.static.cache = {
			-> cacheFactory.ensureCache(name, null)
		}
	}
		
	/**
	 * Preload the content of the NamedCache associated with 
	 * the given domain class.
	 * 
	 * @param grailsClass the domain class.
	 */
	private void preloadDomainCache(GrailsClass grailsClass) {
		def domainClass = grailsClass.clazz
		def ids = domainClass.withCriteria {
			projections { property('id') }
		}
		domainClass.cache().invokeAll(ids, new PreloadRequest())							
	}
	
		
	/**
	 * Add [namedCache].count() and [namedCache].list() gorm-like methods.
	 */
	void registerNamedCacheMethods() {
		
		// Example on using EntryAggregator(s)
		NamedCache.metaClass.count = {
			->
			delegate.aggregate(new Count()) { all() }			
		}
		
		/*
		 * Supported parameters:
		 * max - The maximum number to list
		 * offset - The offset from the first result to list from
		 * order - The order to list by, either "desc" or "asc"
		 * sort - The property name to sort by
		 */
		NamedCache.metaClass.list = { Map params ->			
			def offset = params.offset ? params.int('offset') : 0
			def pagesize = params.max ? params.int('max') : null
			def sort = params.'sort' ?: 'id'
			def order = params.order ?: 'asc'			
			return delegate.entrySet(new GroovyComparator(propertyName:sort, order:order)) {
				if (pagesize) {
					limit((offset/pagesize) as Integer, pagesize) { all() }
				} else {
					all()
				}
			}*.value
		}		
	}
	
	/**
	 * Release all cache references on domain classes.
	 */
	void releaseCacheReferences() {
		application.domainClasses.each { GrailsClass grailsClass ->					
			if (grailsClass.clazz.metaClass.hasMetaMethod('cache')) {
				cacheFactory.releaseCache(grailsClass.clazz.cache())								
			}
		}				
	}
}
